<?php
/**
 * Created by PhpStorm.
 * User: inhere
 * Date: 2016/03/9
 * Time: 15:30
 */

namespace app\handlers;

use app\helpers\exceptions\FileReadException;
use app\helpers\exceptions\FileSystemException;
use app\helpers\exceptions\FileWrittenException;
use Slim;
use app\extensions\MderPage;
use app\parsers\AbstractParser;
use slimExt\exceptions\NotFoundException;
use slimExt\base\Request;

/**
 * Class AbstractHandler
 * @package app\handlers
 *
 * @property MderPage $pager
 * @property AbstractParser $parser
 */
abstract class AbstractHandler
{
    /**
     * MderPage
     * @var MderPage
     */
    protected $pager;

    /**
     * page content parser
     * @var AbstractParser
     */
    protected $parser;

    /**
     * @var string
     */
    protected $error = '';

    protected $isNew = true;// update

    // some action event
    const EVENT_BEFORE_CREATE = 'beforeCreate';
    const EVENT_AFTER_CREATE  = 'afterCreate';
    const EVENT_BEFORE_READ   = 'beforeRead';
    const EVENT_AFTER_READ    = 'afterRead';
    const EVENT_BEFORE_UPDATE = 'beforeUpdate';
    const EVENT_AFTER_UPDATE  = 'beforeUpdate';
    const EVENT_BEFORE_DELETE = 'beforeDelete';
    const EVENT_AFTER_DELETE  = 'beforeDelete';

    /**
     * @param MderPage|string|null $pager
     * @param bool $isNew
     */
    public function __construct($pager=null, $isNew = true)
    {
        $this->setPager($pager);

        $this->isNew = $isNew;
    }

    protected $viewVars = [];

//    abstract public function handle($inputs, $settings);

    /********************************************************************************
     * read content from file
     *******************************************************************************/

    public function read()
    {
        $pager = $this->pager;

        // fire before event
        $this->fire(self::EVENT_BEFORE_READ, $pager->getItemName(), [ $pager->page ]);

        list($content, $oldHash) = $this->doReadContent($pager);

        $this->viewVars['attrs'] = $this->parser->getAttrs(true);

        $this->viewVars['sidebarData'] = $this->parser->collectSidebarData();
       // de($this->parser, $this->viewVars);
        $this->viewVars['content'] = $content;
        $this->viewVars['oldHash'] = $oldHash;

        // fire before event
        $this->fire(self::EVENT_AFTER_READ, $this->pager->getItemName(), [ $pager->page ]);

        return $this;
    }

    /**
     * @param MderPage $pager
     * @return mixed
     */
    protected function doReadContent($pager)
    {
        $oldHash = '';

        // Have already rendered static files
        if ( $pager->rendered ) {
            if ( ( $oldContent = file_get_contents($pager->outputFile) ) === false ) {
                throw new FileReadException("An error occurred while reading the file. File: {$pager->outputFile}");
            }

            //If not enabled detection change, loading already rendered output file content.
            if ( !$this->pager->getSource('detectChange', false)  ) {
                return [ $oldContent, false ];
            }

            $oldHash = md5($oldContent);
        }

        // from parse the data file
        $content  = $this->getParser()->parse();

        return [$content,$oldHash];
    }

    /**
     * cache content to output file
     * @param string $oldHash
     * @param string $rendered
     * @return bool
     */
    public function cacheToFile($oldHash, $rendered)
    {
        // when output file don't exists, read raw data file and save to output folder.
        // OR
        // check it changed ? if no, rewrite $rendered to output file.
        if ( !$oldHash || $oldHash !== md5($rendered) ) {

            // check target path directory
            $outputDir = dirname($this->pager->outputFile);
            if ( $outputDir && !is_dir($outputDir) ) {
                @mkdir($outputDir, 0775, true);

                if ( !is_dir($outputDir)) {
                    throw new FileSystemException("Create a directory there is an error. Dir: $outputDir");
                }
            }

            if ( $this->pager->getOutput('compress', true) ) {
                $rendered = str_replace(["\n","\r\n"], '', $rendered);
            }

            // save to static output folder
            if ( file_put_contents($this->pager->outputFile, $rendered) === false) {
                throw new FileWrittenException("An error occurred while writing to the file. File: {$this->pager->outputFile}");
            }
        }

        return true;
    }

    /********************************************************************************
     * write post content to file
     *******************************************************************************/

    /**
     * @param Request $req
     * @param null|bool $isNew
     * @return bool
     */
    public function write(Request $req, $isNew = null)
    {
        if (null !== $isNew) {
            $this->isNew = (bool)$isNew;
        }

        $pager = $this->pager;
        $pager->findPageFile();

        if ( $data = $this->handleInputs($req) ) {
            list($string, $settings) = $data;

            $this->writeContent($string, $settings);
        }

        return !$this->hasError();
    }

    /**
     * @param $string
     * @param $settings
     * @return bool
     */
    protected function writeContent($string, $settings)
    {
        // fire before event
        $event = $this->isNew() ? self::EVENT_BEFORE_CREATE : self::EVENT_BEFORE_UPDATE;
        $this->fire($event, $this->pager->getItemName(), [ $settings ]);

        if ( $this->hasError() ) {
            return false;
        }

        // do write content to data file
        $this->doWriteContent($string, $this->pager->dataFile);

        // fire after event
        $event = $this->isNew() ? self::EVENT_AFTER_CREATE : self::EVENT_AFTER_UPDATE;
        $this->fire($event, $this->pager->getItemName(), [ $settings ]);

        return true;
    }

    /**
     * @param Request $req
     * @return string
     */
    abstract protected function handleInputs(Request $req);

    protected function beforeCreate($settings)
    {
        $filePath = $this->pager->dataFile;

        if ( $this->isNew && is_file($filePath) ) {
            $this->error = 'File exists. Don\'t allow override it. File: ' . $filePath;
        }

    }

    /**
     * @param $string
     * @param $targetFile
     * @return mixed
     */
    protected function doWriteContent($string, $targetFile)
    {
        // check target path directory
        $this->createDir($targetFile);

        if ( !file_put_contents($targetFile, $string) ) {
            throw new FileWrittenException("Written content to file failure! File: [$targetFile]");
        }

        // chmod
        @chmod($targetFile, 0664);
    }


    /********************************************************************************
     * delete data source file
     *******************************************************************************/

    public function delete()
    {
        $pager = $this->pager;
        $pager->findPageFile();

        // fire `beforeDelete` event
        $this->fire(self::EVENT_BEFORE_DELETE, $pager->getItem()->name);

        $this->doDeleteSourceFile($pager->dataFile);

        $this->fire(self::EVENT_AFTER_DELETE, $pager->getItem()->name);
    }

    /**
     * @param $file
     */
    protected function doDeleteSourceFile($file)
    {
        // false delete, will backup file to trash path.
        if ( Slim::config()->get('sources.falseDelete') ) {
            $trashPath = Slim::alias( Slim::config()->get('sources.trashPath') );
            $trashFile = $trashPath . DIR_SEP . basename($file); //新目录

            $this->createDir($trashFile);

            copy($file,$trashFile); //拷贝到新目录
        }

        unlink($file); //删除旧目录下的文件
    }

    /**
     * @param $event
     * @param $item
     * @param array $args
     */
    protected function fire($event, $item, array $args=[])
    {
        if ( method_exists($this, $event) ) {
            Slim::logger()->debug("Fire event: {$event}, Item name: {$item}");

            // $this->$event();
            call_user_func_array([$this, $event], $args);
        }
    }

    /**
     * @param $filePath
     */
    protected function createDir($filePath)
    {
        // check target path directory
        $fileDir = dirname($filePath);
        if ( $fileDir && !is_dir($fileDir) ) {
            @mkdir($fileDir, 0664, true);

            if ( !is_dir($fileDir)) {
                throw new \RuntimeException("Create a directory there is an error. Dir: $fileDir");
            }
        }
    }


    /********************************************************************************
     * getter/setter
     *******************************************************************************/

    public function getViewVars()
    {
        return $this->viewVars;
    }

    public function getViewVar($key,$default=null)
    {
        return isset($this->viewVars[$key]) ? $this->viewVars[$key] : $default;
    }

    public function addViewVar($key,$value)
    {
        $this->viewVars[$key] = $value;

        return $this;
    }

    public function delViewVar($key)
    {
        if ( isset($this->viewVars[$key]) ) {
            unset($this->viewVars[$key]);
        }

        return $this;
    }

    /**
     * @return MderPage
     */
    public function getPager()
    {
        return $this->pager;
    }

    /**
     * @param  MderPage|string $pager
     * @return static
     */
    public function setPager($pager)
    {
        if ($pager && is_string($pager)) {
            $this->pager = new MderPage($pager);
        } elseif ($pager instanceof MderPage) {
            $this->pager = $pager;
        }

        return $this;
    }

    /**
     * @return AbstractParser
     */
    public function getParser()
    {
        if ( !$this->parser ) {
            $parserClass = $this->pager->getItem()->getOption('parser');

            if ( ! class_exists($parserClass) ) {
                throw new NotFoundException("Parser class [$parserClass] don't exists!!");
            }

            $this->parser = new $parserClass($this->pager) ;
        }

        return $this->parser;
    }

    /**
     * @param AbstractParser|string $parser
     * @return static
     */
    public function setParser($parser)
    {
        if (is_string($parser) && class_exists($parser)) {
            $parser = new $parser($this->pager);
        }

        if ( $parser instanceof AbstractParser) {
            $this->parser = $parser;
        }

        return $this;
    }

    /**
     * @return string
     */
    public function getError()
    {
        return $this->error;
    }

    /**
     * @return bool
     */
    public function isNew()
    {
        return $this->isNew;
    }

    /**
     * @return bool
     */
    public function hasError()
    {
        return (bool)$this->error;
    }
}